#include "mouse.hh"

#include <tools/utility.hh>
#include <sstream>

const unsigned Mouse::LEFT_CLICK;
const unsigned Mouse::SCROLL_CLICK;
const unsigned Mouse::RIGHT_CLICK;
const unsigned Mouse::SCROLL_UP;
const unsigned Mouse::SCROLL_DOWN;

Mouse::Mouse (MenuHandler* mh, Server* s, Camera* c)
  : mh_ (mh)
  , s_ (s)
  , c_ (c)
  , p_intersect (0)
  , r_intersect (0)
{
  // object selection initialization
  obj_select_ = new int[50];
  for (int i = 0; i < 50; ++i)
    obj_select_[i] = -1;
  select_size_ = 0;
  p_intersect = 0;
  r_intersect = 0;

}

Mouse::~Mouse ()
{
  std::cout << "OOOOMMMMMMFFFFFGGGGG DELETE DE SOURIIIIIIIISSSS" << std::endl;
}

void
Mouse::button_pressed (unsigned int button, Camera* c)
{
  MouseInput* mi = new MouseInput ();
  if (!input_.insert (std::pair<unsigned int, MouseInput*>(button, mi)).second)
    {
      delete input_[button];
      input_.erase (button);
      input_.insert (std::pair<unsigned int, MouseInput*>(button, mi));
    }
  input_[button]->pressed (c);
  if (mh_->on_click (button))
    input_[button]->is_interface_set (true);
}

void
Mouse::button_released (unsigned int button, Camera* c)
{
  if (input_.find (button) != input_.end ())
    {
      input_[button]->released (c);
      mh_->on_release (button);
    }
}

void
Mouse::erase (unsigned int button)
{
  if (input_.find (button) != input_.end ())
    delete input_[button];

  input_.erase (button);
}

bool
Mouse::button_check (unsigned int button) const
{
  std::map<unsigned int, MouseInput*>::const_iterator it = input_.end ();

  return (input_.find (button) != it);
}

void
Mouse::catch_mouse_pos (unsigned int button, Camera* c)
{
  if (input_.find (button) != input_.end ())
    input_[button]->catch_pos (c);
}

void
Mouse::treat ()
{
  std::map<unsigned int, MouseInput*>::iterator it;
  std::map<unsigned int, MouseInput*>::iterator it_tmp;
  bool deleted = false;

  it = input_.begin ();
  while (it != input_.end ())
  {
    deleted = false;
    if (it->second->is_interface ())
    {
      // treat interface
      ++it;
    }
    else
    {
      // treat in the game
      if (it->first == Mouse::SCROLL_UP)
      {
        c_->scroll_up ();
        deleted = true;
      }
      else if (it->first == Mouse::SCROLL_DOWN)
      {
        c_->scroll_down ();
        deleted = true;
      }
      else if (it->first == Mouse::LEFT_CLICK)
        deleted = handle_left_click ();
      else if (it->first == Mouse::RIGHT_CLICK)
        deleted = handle_right_click ();
      else
        deleted = true;

      if (deleted)
      {
        it_tmp = it;
        ++it;
        delete input_[it_tmp->first];
        input_.erase (it_tmp);
      }
      else
        ++it;
    }
  }
}

void
Mouse::reset_selection ()
{
  select_size_ = 0;
  obj_select_[0] = -1;
  std::map<unsigned int, MouseInput*>::iterator it;

  for (it = input_.begin (); it != input_.end (); ++it)
    delete it->second;

  input_.clear ();
}

void
Mouse::dispatch () const
{
  if (r_intersect && p_intersect)
    {
      glColor3ub (0, 255, 0);
      glBegin (GL_LINES);

      glVertex3d (0.5, (*p_intersect) (1), (*p_intersect) (2));
      glVertex3d (0.5, (*p_intersect) (1), (*r_intersect) (2));

      glVertex3d (0.5, (*p_intersect) (1), (*r_intersect) (2));
      glVertex3d (0.5, (*r_intersect) (1), (*r_intersect) (2));

      glVertex3d (0.5, (*r_intersect) (1), (*r_intersect) (2));
      glVertex3d (0.5, (*r_intersect) (1), (*p_intersect) (2));

      glVertex3d (0.5, (*r_intersect) (1), (*p_intersect) (2));
      glVertex3d (0.5, (*p_intersect) (1), (*p_intersect) (2));

      glEnd ();
    }
}

bool
Mouse::handle_left_click ()
{
  MouseInput* mi = 0;

  std::map<unsigned int, MouseInput*>::const_iterator it = input_.find (Mouse::LEFT_CLICK);
  if (it != input_.end ())
    mi = it->second;

  mi->gen_press_ray ();
  mi->gen_release_ray ();

  Ray* press_ray = mi->p_ray_get ();
  Ray* release_ray = mi->r_ray_get ();

  Vector3f p1 (0, 0, 0);
  Vector3f p2 (0, 1, 0);
  Vector3f p3 (0, 0, 1);

  Plane p (p1, p2, p3);

  if (!p_intersect)
    p_intersect = press_ray->intersect (&p);

  if (r_intersect)
    delete r_intersect;
  r_intersect = release_ray->intersect (&p);

  if (mi->is_released ())
  {
    obj_selection (mi);

    delete p_intersect;
    delete r_intersect;
    r_intersect = 0;
    p_intersect = 0;
    return true;
  }

  return false;
}

bool
Mouse::handle_right_click ()
{
  MouseInput* mi = 0;

  std::map<unsigned int, MouseInput*>::const_iterator it = input_.find (Mouse::RIGHT_CLICK);
  if (it != input_.end ())
    mi = it->second;

  mi->gen_press_ray ();
  mi->gen_release_ray ();

  Ray* press_ray = mi->p_ray_get ();

  Vector3f p1 (0, 0, 0);
  Vector3f p2 (0, 1, 0);
  Vector3f p3 (0, 0, 1);

  Plane p (p1, p2, p3);

  if (!p_intersect)
    p_intersect = press_ray->intersect (&p);

  Vector3f res = *p_intersect;
  res (0, (*p_intersect) (2));
  res (2, (*p_intersect) (0));

  if (mi->is_released ())
  {
    if (select_size_)
    {
      std::string msg;
      // Size of the request.
      msg += select_size_;
      for (int i = 0; i < select_size_; ++i)
      {
        msg += obj_select_[i];
        // FIXME: call the PathFinder.
        // size of the vect.
        msg += 1;
        msg += res.tonet ();
        s_->nm ()->send (RType::MOVE, msg);
      }
    }
    delete p_intersect;
    delete r_intersect;
    r_intersect = 0;
    p_intersect = 0;
    return true;
  }

  return false;
}

void
Mouse::obj_selection (MouseInput* mi)
{
  const tbb::concurrent_hash_map<int, boost::shared_ptr<Object> >* objmap = s_->objmap ();
  tbb::concurrent_hash_map<int, boost::shared_ptr<Object> >::accessor acc;

  for (int i = 0; i < select_size_; ++i)
  {
    if (objmap->find (acc, obj_select_[i]))
      acc->second->selected (false);
    else
      assert (false);
  }

  select_size_ = 0;
  obj_select_[0] = -1;

  if (((*r_intersect) - (*p_intersect)).get_norme () > 5)
    multi_selection (p_intersect, r_intersect);
  else
    mono_selection (mi->p_ray_get ());
}

void
Mouse::mono_selection (Ray* ray)
{
  const tbb::concurrent_hash_map<int, boost::shared_ptr<Object> >* objmap = s_->objmap ();

  float min_dist = 10000;
  float dist = 0;
  Vector3f tmp_obj;

  for (tbb::concurrent_hash_map<int, boost::shared_ptr<Object> >::const_iterator it = objmap->begin ();
       it != objmap->end (); ++it)
  {
    if (it->second->bounding ()->dispatch (it->second->pos (), ray))
    {
      tmp_obj = it->second->pos ();
      dist = tmp_obj (0);
      tmp_obj (0, tmp_obj (2));
      tmp_obj (2, dist);
      dist = (c_->pos_get () - tmp_obj).get_norme ();
      if (dist < min_dist)
      {
	obj_select_[0] = it->first;
	it->second->selected (true);
	min_dist = dist;
      }
    }
  }

  if (obj_select_[0] != -1)
    select_size_ = 1;
}

void
Mouse::multi_selection (Vector3f* base, Vector3f* end)
{
  const tbb::concurrent_hash_map<int, boost::shared_ptr<Object> >* objmap = s_->objmap ();

  Object* tmp = 0;
  Vector3f pos;

  Vector2f min;
  Vector2f max;

  if ((*base) (1) < (*end) (1))
  {
    min (0, (*base) (1));
    max (0, (*end) (1));
  }
  else
  {
    min (0, (*end) (1));
    max (0, (*base) (1));
  }

  if ((*base) (2) < (*end) (2))
  {
    min (1, (*base) (2));
    max (1, (*end) (2));
  }
  else
  {
    min (1, (*end) (2));
    max (1, (*base) (2));
  }

  for (tbb::concurrent_hash_map<int, boost::shared_ptr<Object> >::const_iterator it = objmap->begin ();
      it != objmap->end (); ++it)
  {
    tmp = it->second.get ();
    pos = tmp->pos ();

    if (pos (1) > min (0) && pos (1) < max (0) &&
        pos (0) > min (1) && pos (0) < max (1))
    {
      obj_select_[select_size_] = it->first;
      it->second->selected (true);
      ++select_size_;
    }
  }
}
